﻿using Microsoft.AspNetCore.Authentication;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace IdentityDemo.CookiesJwt
{
    public class TicketJsonSerializer : IDataSerializer<AuthenticationTicket>
    {

        public static TicketJsonSerializer Default { get; } = new TicketJsonSerializer();

        public byte[] Serialize(AuthenticationTicket ticket)
        {
            if (ticket == null)
            {
                throw new ArgumentNullException(nameof(ticket));
            }
            var ticketResult = new TicketResult()
            {
                AuthenticationScheme = ticket.AuthenticationScheme,
                Identities = ticket.Principal.Identities.Select(r => WriterIdentitie(r)),
                PropertiesItems= ticket.Properties.Items
            };

            var ticketStr = JsonConvert.SerializeObject(ticketResult, new JsonSerializerSettings()
            {
                NullValueHandling = NullValueHandling.Ignore
            });
            var bytes = Encoding.Default.GetBytes(ticketStr);
            return bytes;
        }



        public AuthenticationTicket Deserialize(byte[] data)
        {
            var ticketStr = Encoding.Default.GetString(data);
            var ticket = JsonConvert.DeserializeObject<TicketResult>(ticketStr);
            var identities = ticket.Identities.Select(r => ReadIdentitie(r));
            var properties = new AuthenticationProperties(ticket.PropertiesItems);

            var model = new AuthenticationTicket(new ClaimsPrincipal(identities), properties, ticket.AuthenticationScheme);
            return model;
        }

        private static IdentitieResult WriterIdentitie(ClaimsIdentity claimsIdentity)
        {
            if (claimsIdentity == null)
            {
                throw new ArgumentNullException(nameof(claimsIdentity));
            }
            var identitieResult = new IdentitieResult()
            {
                AuthenticationType = claimsIdentity.AuthenticationType,
                NameClaimType = WriterWithDefault(claimsIdentity.NameClaimType, ClaimsIdentity.DefaultNameClaimType),
                RoleClaimType = WriterWithDefault(claimsIdentity.RoleClaimType, ClaimsIdentity.DefaultRoleClaimType),
                BootstrapContext = claimsIdentity.BootstrapContext,
                Claims = claimsIdentity.Claims?.Select(claim => new ClaimResult()
                {
                    Type = WriterWithDefault(claim.Type, claim.Subject?.NameClaimType ?? ClaimsIdentity.DefaultNameClaimType),
                    ValueType = WriterWithDefault(claim.ValueType, ClaimValueTypes.String),
                    Value = claim.Value,
                    Issuer = WriterWithDefault(claim.Issuer, ClaimsIdentity.DefaultIssuer),
                    OriginalIssuer = WriterWithDefault(claim.OriginalIssuer, claim.Issuer),
                    Properties = claim.Properties
                })
            };
            if (claimsIdentity.Actor != null)
            {
                identitieResult.Actor = WriterIdentitie(claimsIdentity.Actor);
            }
            return identitieResult;
        }
        private static string WriterWithDefault(string value,string defaultValue)
        {
            if (string.Equals(value, defaultValue, StringComparison.Ordinal))
            {
                return null;
            }
            else
            {
                return value;
            }
        }

        private static ClaimsIdentity ReadIdentitie(IdentitieResult identitieResult)
        {
            if (identitieResult == null)
            {
                throw new ArgumentNullException(nameof(identitieResult));
            }
            var authenticationType = identitieResult.AuthenticationType;
            var nameClaimType = ReadWithDefault(identitieResult.NameClaimType, ClaimsIdentity.DefaultNameClaimType);
            var roleClaimType = ReadWithDefault(identitieResult.RoleClaimType, ClaimsIdentity.DefaultRoleClaimType);

            var model = new ClaimsIdentity(authenticationType, nameClaimType, roleClaimType);
            model.BootstrapContext = identitieResult.BootstrapContext;
            
            foreach (var claimResult in identitieResult.Claims)
            {
                var type = ReadWithDefault(claimResult.Type, model.NameClaimType);
                var value = claimResult.Value;
                var valueType = ReadWithDefault(claimResult.ValueType, ClaimValueTypes.String);
                var issuer = ReadWithDefault(claimResult.Issuer, ClaimsIdentity.DefaultIssuer);
                var originalIssuer = ReadWithDefault(claimResult.OriginalIssuer, issuer);
                var claim = new Claim(type, value, valueType, issuer, originalIssuer, model);
                foreach (var propertie in claimResult.Properties)
                {
                    claim.Properties.Add(propertie);
                }
                model.AddClaim(claim);
            }
            if (identitieResult.Actor != null)
            {
                model.Actor = ReadIdentitie(identitieResult.Actor);
            }
            return model;
        }
        private static string ReadWithDefault(string value, string defaultValue)
        {
            if (string.IsNullOrEmpty(value))
            {
                return defaultValue;
            }
            return value;
        }
        public class TicketResult
        {
            public string AuthenticationScheme { get; set; }

            public IEnumerable<IdentitieResult> Identities { get; set; }

            public IDictionary<string,string> PropertiesItems { get; set; }

        }
        public class IdentitieResult
        {
            public string AuthenticationType { get; set; }

            public string NameClaimType { get; set; }

            public string RoleClaimType { get; set; }

            public object BootstrapContext { get; set; }

            public IdentitieResult Actor { get; set; }

            public IEnumerable<ClaimResult> Claims{ get; set; }
        }

        public class ClaimResult
        {
            public string Type { get; set; }

            public string ValueType { get; set; }

            public string Value { get; set; }
            public string Issuer { get; set; }

            public string OriginalIssuer { get; set; }

            public IDictionary<string, string> Properties { get; set; }

        }
    }
}
